package app;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

/**
 * 
 * @author 116@lab0.net
 * 
 *         Describes a Hungarian rings board. The schema below gives the initial state of this puzzle. R = red. B =
 *         blue. W = black (B already used, so to remember it: white = !black). Y = yellow.
 * 
 *         <pre>
 * 
 *         The balls in the ring: colors.
 * 
 *           LEFT RING        RIGHT RING
 * 
 *              B5 B6 B7 B8  W5 W6 W7 W8
 *  T   ^     B4           W4           W9     |   T
 *  u   |   B3           W3  R0           Y0   |   u
 *  r   |   B2     1     W2  R1     2     Y1   |   r
 *  n   |   B1           W1  R2           Y2   |   n
 *  1   |   B0           W0  R3           Y3   v   2
 *            R9           R4           Y4
 *              R8 R7 R6 R5  Y8 Y7 Y6 Y5
 * 
 *          The balls in the ring: Lists.
 * 
 *              L10 L11 L12 L13  R0  R1  R2  R3
 *            L9               Ct               R4
 *          L8               l3  r0               R5
 *          L7               l2  r1               R6
 *          L6               l1  r2               R7
 *          L5               l0  r3               R8
 *            L4               Cb               R9
 *              L3  L2  L1  L0   R13 R12 R11 R10
 * 
 * </pre>
 * 
 * 
 */
public class Board
{
    /**
     * Initial value of the ball named Ct = W4
     */
    private Ball             topCentralBall;
    
    /**
     * Initial value of the ball named Cb = R4
     */
    private Ball             bottomCentralBall;
    
    /**
     * Initial value of the list named L = { R5, R6, R7, R8, R9, B0, B1, B2, B3, B4, B5, B6, B7, B8 }
     */
    private LinkedList<Ball> leftBalls;
    
    /**
     * Initial value of the list named R = { W5, W6, W7, W8, W9, Y0, Y1, Y2, Y3, Y4, Y5, Y6, Y7, Y8 }
     */
    private LinkedList<Ball> rightBalls;
    
    /**
     * Initial value of the list named l = { W0, W1, W2, W3 }
     */
    private LinkedList<Ball> centerLeftBalls;
    
    /**
     * Initial value of the list named r = { R0, R1, R2, R3 }
     */
    private LinkedList<Ball> centerRightBalls;
    
    /**
     * Contains the history of the moves on this board. If <code>true</code> it is a <code>turn1()</code>. Otherwise, it
     * is a <code>turn2()</code>. This list is not copied when using the copy constructor.
     */
    private List<Boolean>    history = new ArrayList<>(0);
    
    /**
     * Creates a new finished puzzle.
     */
    public Board()
    {
        topCentralBall = new Ball(BallColor.BLACK, "W4");
        bottomCentralBall = new Ball(BallColor.RED, "R4");
        
        leftBalls = new LinkedList<>();
        rightBalls = new LinkedList<>();
        centerLeftBalls = new LinkedList<>();
        centerRightBalls = new LinkedList<>();
        
        for (int i = 0; i < 4; ++i)
        {
            centerLeftBalls.add(new Ball(BallColor.BLACK, "W" + i));
            centerRightBalls.add(new Ball(BallColor.RED, "R" + i));
        }
        
        for (int i = 5; i < 10; ++i)
        {
            rightBalls.add(new Ball(BallColor.BLACK, "W" + i));
            leftBalls.add(new Ball(BallColor.RED, "R" + i));
        }
        
        for (int i = 0; i < 9; ++i)
        {
            rightBalls.add(new Ball(BallColor.YELLOW, "Y" + i));
            leftBalls.add(new Ball(BallColor.BLUE, "B" + i));
        }
    }
    
    /**
     * Copies the position of the other board balls.
     * 
     * @param other
     *            The board to copy.
     */
    public Board(Board other)
    {
        this.topCentralBall = other.getTopCentralBall();
        this.bottomCentralBall = other.getBottomCentralBall();
        
        this.leftBalls = new LinkedList<>(other.getLeftBalls());
        this.rightBalls = new LinkedList<>(other.getRightBalls());
        this.centerLeftBalls = new LinkedList<>(other.getCenterLeftBalls());
        this.centerRightBalls = new LinkedList<>(other.getCenterRightBalls());
    }
    
    public Ball getTopCentralBall()
    {
        return topCentralBall;
    }
    
    public Ball getBottomCentralBall()
    {
        return bottomCentralBall;
    }
    
    /**
     * 
     * @return An unmodifiable list of the left balls list.
     */
    public List<Ball> getLeftBalls()
    {
        return Collections.unmodifiableList(leftBalls);
    }
    
    /**
     * 
     * @return An unmodifiable list of the right balls list.
     */
    public List<Ball> getRightBalls()
    {
        return Collections.unmodifiableList(rightBalls);
    }
    
    /**
     * 
     * @return An unmodifiable list of the center left balls list.
     */
    public List<Ball> getCenterLeftBalls()
    {
        return Collections.unmodifiableList(centerLeftBalls);
    }
    
    /**
     * 
     * @return An unmodifiable list of the center right balls list.
     */
    public List<Ball> getCenterRightBalls()
    {
        return Collections.unmodifiableList(centerRightBalls);
    }
    
    /**
     * 
     * @param ballPosition
     *            the position of the ball in the rings.
     * @return the ball at this position;
     */
    public Ball getByPosition(BallPosition ballPosition)
    {
        switch (ballPosition)
        {
            case l0:
                return centerLeftBalls.get(0);
            case l1:
                return centerLeftBalls.get(1);
            case l2:
                return centerLeftBalls.get(2);
            case l3:
                return centerLeftBalls.get(3);
                
            case r0:
                return centerRightBalls.get(0);
            case r1:
                return centerRightBalls.get(1);
            case r2:
                return centerRightBalls.get(2);
            case r3:
                return centerRightBalls.get(3);
                
            case L0:
                return leftBalls.get(0);
            case L1:
                return leftBalls.get(1);
            case L2:
                return leftBalls.get(2);
            case L3:
                return leftBalls.get(3);
            case L4:
                return leftBalls.get(4);
            case L5:
                return leftBalls.get(5);
            case L6:
                return leftBalls.get(6);
            case L7:
                return leftBalls.get(7);
            case L8:
                return leftBalls.get(8);
            case L9:
                return leftBalls.get(9);
            case L10:
                return leftBalls.get(10);
            case L11:
                return leftBalls.get(11);
            case L12:
                return leftBalls.get(12);
            case L13:
                return leftBalls.get(13);
                
            case R0:
                return rightBalls.get(0);
            case R1:
                return rightBalls.get(1);
            case R2:
                return rightBalls.get(2);
            case R3:
                return rightBalls.get(3);
            case R4:
                return rightBalls.get(4);
            case R5:
                return rightBalls.get(5);
            case R6:
                return rightBalls.get(6);
            case R7:
                return rightBalls.get(7);
            case R8:
                return rightBalls.get(8);
            case R9:
                return rightBalls.get(9);
            case R10:
                return rightBalls.get(10);
            case R11:
                return rightBalls.get(11);
            case R12:
                return rightBalls.get(12);
            case R13:
                return rightBalls.get(13);
                
            case Ct:
                return topCentralBall;
            case Cb:
                return bottomCentralBall;
                
            default:
                // must not happen
                assert (false);
                return null;
        }
    }
    
    /**
     * Turns the first disk clockwise.
     */
    public void turn1()
    {
        leftBalls.addFirst(bottomCentralBall);
        bottomCentralBall = centerRightBalls.removeLast();
        centerRightBalls.addFirst(topCentralBall);
        topCentralBall = leftBalls.removeLast();
        history.add(true);
    }
    
    /**
     * equivalent to 19 calls to <code>turn1()</code>
     */
    public void reverseTurn1()
    {
        // Laziness caught me
        for (int i = 0; i < 19; ++i)
        {
            turn1();
        }
    }
    
    /**
     * Turns the second disk clockwise.
     */
    public void turn2()
    {
        rightBalls.addFirst(topCentralBall);
        topCentralBall = centerLeftBalls.removeLast();
        centerLeftBalls.addFirst(bottomCentralBall);
        bottomCentralBall = rightBalls.removeLast();
        history.add(false);
    }
    
    /**
     * equivalent to 19 calls to <code>turn2()</code>
     */
    public void reverseTurn2()
    {
        // Laziness caught me
        for (int i = 0; i < 19; ++i)
        {
            turn2();
        }
    }
    
    public void shuffle()
    {
        for (int i = 0; i < 1000; ++i)
        {
            if (Math.random() > 0.5)
            {
                turn1();
            }
            else
            {
                turn2();
            }
        }
    }
    
    /**
     * Compares the differences between <code>this</code> board and the <code>other</code> board. Takes only the color
     * of the balls as a parameter to count the differences.
     * 
     * @param other
     *            The board to do the comparison with.
     * @return the number of differences.
     */
    public int differences(Board other)
    {
        int total = 0;
        if (this.bottomCentralBall != other.bottomCentralBall)
        {
            total++;
        }
        if (this.topCentralBall != other.topCentralBall)
        {
            total++;
        }
        
        for (int i = 0; i < 4; ++i)
        {
            if (!this.centerLeftBalls.get(i).equals(other.centerLeftBalls.get(i)))
            {
                total++;
            }
            if (!this.centerRightBalls.get(i).equals(other.centerRightBalls.get(i)))
            {
                total++;
            }
        }
        
        for (int i = 0; i < 14; ++i)
        {
            if (!this.leftBalls.get(i).equals(other.leftBalls.get(i)))
            {
                total++;
            }
            if (!this.rightBalls.get(i).equals(other.rightBalls.get(i)))
            {
                total++;
            }
        }
        
        return total;
    }
    
    /**
     * Executes the given sequence of actions on this board.
     * 
     * @param actions
     *            The actions to do, with the same convention as the history.
     */
    public void execute(boolean[] actions)
    {
        for (boolean b : actions)
        {
            if (b)
            {
                turn1();
            }
            else
            {
                turn2();
            }
        }
    }
    
}
